import Element from '../element';
import TagParser from './TagParser';
import Utils from '../Utils/Utils';
import {
    HTML_TAG_TYPE,
    AST_NODE_TYPE,
    ERROR_TYPE
} from '../constant';

export default class HtmlParser {

    htmlStr = undefined;
    ElementStack = null;

    static tagParser = new TagParser();

    constructor() {}

    // Parser 生命周期函数

    init(htmlStr) {
        this.htmlStr = htmlStr;
        this.ElementStack = [];
        this.ElementStack.push(new Element());
    }

    prefix() {
        this.htmlStr = this.htmlStr.replace('\n', "");
        this.htmlStr = this.htmlStr.replace('\r', "");
        this.htmlStr = this.htmlStr.replace('\r\n', "");
    }

    destroy() {
        delete this.htmlStr;
        delete this.ElementStack;
    }

    parse(htmlStr) {
        this.init(htmlStr);
        this.prefix();
        this.parseHTML();
        let rootElement = this.ElementStack.pop();
        this.destroy();
        return rootElement;
    }

    // 内部功能函数

    parseHTML() {
        let htmlTag = undefined;
        do {
            try {
                // 检查是否有文本内容
                this.parseTextContent();
                // 检查是否有标签
                htmlTag = this.parseHtmlTag();
                if (!Utils.isUnDef(htmlTag)) {
                    // 如果是注释则不进行分析
                    if (HtmlParser.checkTagIfComment(htmlTag)) {
                        continue;
                    }
                    // 检查标签的声明方式， 是 “<xxx>” ，“</xxx>” ，“<xxx/>” 中的哪一种
                    let htmlTagType = HtmlParser.getHtmlTagType(htmlTag);

                    // 根据不同的标签类型执行相应的动作
                    switch (htmlTagType) {
                        case (HTML_TAG_TYPE.OPEN): {
                            this.parseOpenTag(htmlTag);
                            break;
                        }
                        case (HTML_TAG_TYPE.CLOSE): {
                            this.parseCloseTag(htmlTag);
                            break;
                        }
                        case (HTML_TAG_TYPE.SINGLE): {
                            this.parseSingleTag(htmlTag);
                            break;
                        }
                    }
                }
            } catch (exception) {
                switch (exception.type) {
                    case (ERROR_TYPE.NO_FINISH_TAG): {
                        console.error(exception);
                        htmlTag = undefined;
                        break;
                    }
                    case (ERROR_TYPE.INVALID_TAG_TYPE): {
                        console.error(exception);
                        htmlTag = undefined;
                        break;
                    }
                    case (ERROR_TYPE.EXCEPTED_TAG_NAME_TYPE): {
                        console.error(exception);
                        htmlTag = undefined;
                        break;
                    }
                }
            }
        } while (htmlTag !== undefined);
    }

    /**
     * 从 HTML 字符串中 获取下一个标签的位置，
     * 而从 0 -- 下一个标签位置的起始索引都是文本内容
     */
    parseTextContent() {
        let matcher = /\<[^<>]*\>/;
        let matchResult = this.htmlStr.match(matcher);
        if (!Utils.isNull(matchResult)) {
            let index = matchResult.index;
            let textContent = this.htmlStr.substring(0, index).trim();
            this.htmlStr = this.htmlStr.substr(index);

            if (textContent && textContent !== "") {
                let textElement = new Element();
                textElement.type = "text";
                textElement.astType = AST_NODE_TYPE.TEXT;
                textElement.textContent = textContent;

                let parentElement = this.ElementStack.pop();
                textElement.parent = parentElement;
                parentElement.children.push(textElement);
                this.ElementStack.push(parentElement);
            }
        }
    }

    /**
     * 从 HTML 字符串中 获取一个标签，并将这个标签从 HTML 字符串中删除
     */
    parseHtmlTag() {
        let matcher = /\<[^<>]*\>/;
        let matchResult = this.htmlStr.match(matcher);
        if (!Utils.isNull(matchResult)) {
            let tagStr = matchResult[0];
            this.htmlStr = this.htmlStr.replace(tagStr, "");
            return tagStr;
        }
        return undefined;
    }

    /**
     * 分析开口标签
     * @param {String} htmlTag 
     */
    parseOpenTag(htmlTag) {
        let element = HtmlParser.tagParser.parse(htmlTag);
        this.ElementStack.push(element);
    }

    /**
     * 分析闭口标签
     * @param {String} htmlTag 
     */
    parseCloseTag(htmlTag) {
        let htmlType = HtmlParser.getHtmlCloseTagHtmlType(htmlTag);
        let topElement = this.ElementStack.pop();
        if (topElement.type === htmlType) {
            // 创建负责关系
            let parentNode = this.ElementStack.pop();
            topElement.parent = parentNode;
            parentNode.children.push(topElement);

            // 计算 该元素 的 textContent属性
            // for (let i=0; topElement.children && i<topElement.children.length; i++) {
            //     let text = topElement.children[i].textContent;
            //     topElement.textContent += text;
            // }

            this.ElementStack.push(parentNode);
        } else {
            let exception = new Error(
                `发现一个无对应结束标签的HTML开始标签
                标签为: ${htmlTag}
                分析字符串至: ${this.htmlStr}`
            );
            exception.type = ERROR_TYPE.NO_FINISH_TAG;
            throw exception;
        }
    }

    /**
     * 分析独立标签
     * @param {String} htmlTag 
     */
    parseSingleTag(htmlTag) {
        let element = HtmlParser.tagParser.parse(htmlTag);
        let parentNode = this.ElementStack.pop();
        element.parent = parentNode;
        parentNode.children.push(element);
        this.ElementStack.push(parentNode);
    }

    /**
     * 根据 HTML 标签字符串， 获取标签字符串的类型
     * @param {String} htmlTag 
     */
    static getHtmlTagType(htmlTag) {
        let openMatcher = /\<\s*[^<>\/]*\s*\>/;
        let closeMatcher = /\<\s*\/\s*[^<>\/]*\s*\>/;
        let singleMatcher = /\<\s*[^<>\/]*\s*\/\s*\>/;

        if (!Utils.isNull(htmlTag.match(openMatcher))) {
            return HTML_TAG_TYPE.OPEN;
        } else if (!Utils.isNull(htmlTag.match(closeMatcher))) {
            return HTML_TAG_TYPE.CLOSE;
        } else if (!Utils.isNull(htmlTag.match(singleMatcher))) {
            return HTML_TAG_TYPE.SINGLE;
        } else {
            let exception = new Error(
                `发现一个无法辨别的标签，是否使用了非法的书写方式？
                标签为: ${htmlTag}
                分析字符串至: ${this.htmlStr}`
            );
            exception.type = ERROR_TYPE.INVALID_TAG_TYPE;
            throw exception;
        }
    }

    /**
     * 获得 HTML 闭口标签的 HTML 类型
     * @param {String} htmlTag 
     */
    static getHtmlCloseTagHtmlType(htmlTag) {
        let matcher = /\<\s*\/\s*[a-zA-Z0-9]*/;
        let matchResult = htmlTag.match(matcher);
        if (!Utils.isNull(matchResult)) {
            let matchStr = matchResult[0];
            let start = matchStr.indexOf('/') + 1;
            while (matchStr[start] === ' ') {
                start++;
            }
            let htmlType = matchStr.substring(start, matchStr.length);
            return htmlType;
        } else {
            let exception = new Error(
                `发现一个无法分析的标签，是否使用了非法的书写方式？
                标签为: ${htmlTag}
                分析字符串至: ${this.htmlStr}`
            );
            exception.type = ERROR_TYPE.INVALID_TAG_TYPE;
            throw exception;
        }
    }

    /**
     * 判断标签是否是注释标签
     * @param {String} htmlTag 
     */
    static checkTagIfComment(htmlTag) {
        let matcher = /\<\!\-\-[\s\S]*\-\-\>/m;
        let matchResult = htmlTag.match(matcher);
        if (Utils.isNull(matchResult)) {
            return false;
        } else {
            return true;
        }
    }

};